﻿/*************************************************************************************
 * 工    具：   HK_ScenePro
 * 
 * 描    述：   HK_ScenePro是一个用于增强 Unity 编辑器中 Scene 视图功能的工具,主要功能如下：
 *             1. 提供类似 Photoshop 中的标尺功能，可以拖拽参考线，方便用于排版等工作。
 * 
 * 版    本：   V1.0
 * 作    者：   京产肠饭
 * 
 * 创    建：   2024/10/02  V1.0
 * 
 * 链    接：   https://gitee.com/JingChanChangFan/hk_-unity-tools
 *************************************************************************************/

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

namespace HKTools
{
    /// <summary>
    /// Scene 窗口增强工具，提供标尺和参考线功能。
    /// </summary>
    [InitializeOnLoad]
    public class HK_ScenePro
    {
        public static void ChangeState(bool isActive)
        {
            if (isActive)
            {
                SceneView.duringSceneGui += OnSceneGUI;
                isVisible = false;
            }
            else
            {
                SceneView.duringSceneGui -= OnSceneGUI;
                isVisible = false;
            }

            SceneView.RepaintAll();
        }

        /// <summary>
        /// 参考线的类。
        /// </summary>
        public class GuideLine
        {
            public float positionInWorld;    // 参考线在世界坐标中的位置
            public bool isHorizontal;        // 是否是水平参考线
        }

        // 所有参考线的列表
        static List<GuideLine> guideLines = new List<GuideLine>();

        // 参考线交互相关变量
        static bool isDraggingLine = false;
        static GuideLine curDraggedLine = null;
        static GuideLine curSelectedLine = null;

        // 标尺和参考线的可见性控制
        static bool isVisible = false;

        /// <summary>
        /// Scene 视图的 GUI 回调方法。
        /// </summary>
        /// <param name="sceneView">当前的 SceneView</param>
        static void OnSceneGUI(SceneView sceneView)
        {
            // 处理用户在 Scene 视图中的输入
            HandleInput(sceneView);

            // 如果标尺不可见，直接返回
            if (!isVisible)
                return;

            // 开始绘制 GUI
            Handles.BeginGUI();
            // 绘制标尺
            DrawRulers(sceneView);
            Handles.EndGUI();

            // 只在 2D 模式或正交相机模式下，绘制参考线
            if (sceneView.in2DMode || (sceneView.camera != null && sceneView.camera.orthographic))
            {
                DrawLines(sceneView);
            }
        }

        /// <summary>
        /// 处理用户在 Scene 视图中的输入
        /// </summary>
        /// <param name="sceneView">当前的 SceneView</param>
        static void HandleInput(SceneView sceneView)
        {
            Event e = Event.current;
            Vector2 mousePos = e.mousePosition;

            // 监听 Ctrl+R 键组合，切换标尺和参考线的显示
            if (e.type == EventType.KeyDown && e.keyCode == KeyCode.R && e.control)
            {
                isVisible = !isVisible; // 切换可见性
                SceneView.RepaintAll();
                e.Use();
            }

            // 如果标尺不可见，不处理其他输入
            if (!isVisible)
                return;

            // 将鼠标屏幕坐标转换为世界坐标
            Vector2 mouseWorldPos = HandleUtility.GUIPointToWorldRay(mousePos).origin;

            // 标尺区域定义
            Rect horizontalRulerRect = new Rect(20, 0, sceneView.position.width - 20, 20);
            Rect verticalRulerRect = new Rect(0, 20, 20, sceneView.position.height - 20);

            // 处理鼠标按下事件
            if (e.type == EventType.MouseDown && e.button == 0)
            {
                // 检测是否点击在已有的参考线上
                GuideLine curLine = FindLineAtPosition(mouseWorldPos);
                if (curLine != null)
                {
                    // 选中并开始拖拽已有的参考线
                    curSelectedLine = curLine;
                    isDraggingLine = true;
                    curDraggedLine = curLine;
                    e.Use();
                }
                else if (horizontalRulerRect.Contains(mousePos))
                {
                    // 从水平标尺创建新的水平参考线
                    CreateNewLine(true, mousePos.y);
                    e.Use();
                }
                else if (verticalRulerRect.Contains(mousePos))
                {
                    // 从垂直标尺创建新的垂直参考线
                    CreateNewLine(false, mousePos.x);
                    e.Use();
                }
                else
                {
                    // 点击其他区域，清除选中的参考线
                    curSelectedLine = null;
                }
            }

            // 处理鼠标拖拽事件
            if (e.type == EventType.MouseDrag && isDraggingLine)
            {
                UpdateDraggedLine(e.mousePosition);
                sceneView.Repaint(); // 重绘 Scene 视图
                e.Use();
            }

            // 处理鼠标抬起事件
            if (e.type == EventType.MouseUp && isDraggingLine)
            {
                isDraggingLine = false;
                curDraggedLine = null;
                e.Use();
            }

            // 处理删除参考线的事件
            if (e.type == EventType.KeyDown && e.keyCode == KeyCode.Delete)
            {
                if (curSelectedLine != null)
                {
                    guideLines.Remove(curSelectedLine);
                    curSelectedLine = null;
                    SceneView.RepaintAll();
                    e.Use();
                }
            }
        }

        /// <summary>
        /// 创建新的参考线
        /// </summary>
        /// <param name="isHorizontal">是否为水平参考线</param>
        /// <param name="mousePosition">鼠标位置</param>
        static void CreateNewLine(bool isHorizontal, float mousePosition)
        {
            isDraggingLine = true;
            Vector2 worldPos = HandleUtility.GUIPointToWorldRay(new Vector2(isHorizontal ? 0 : mousePosition, isHorizontal ? mousePosition : 0)).origin;

            curDraggedLine = new GuideLine
            {
                isHorizontal = isHorizontal,
                positionInWorld = isHorizontal ? worldPos.y : worldPos.x
            };

            guideLines.Add(curDraggedLine);
            // 选中新创建的参考线
            curSelectedLine = curDraggedLine;
        }

        /// <summary>
        /// 更新正在拖拽的参考线位置
        /// </summary>
        /// <param name="mousePosition">当前鼠标位置</param>
        static void UpdateDraggedLine(Vector2 mousePosition)
        {
            Vector2 mouseWorldPos = HandleUtility.GUIPointToWorldRay(mousePosition).origin;

            if (curDraggedLine != null)
            {
                if (curDraggedLine.isHorizontal)
                {
                    curDraggedLine.positionInWorld = mouseWorldPos.y;
                }
                else
                {
                    curDraggedLine.positionInWorld = mouseWorldPos.x;
                }
            }
        }

        /// <summary>
        /// 在指定的世界坐标位置查找参考线
        /// </summary>
        /// <param name="mouseWorldPos">鼠标的世界坐标位置</param>
        /// <returns>找到的参考线，如果没有则为 null</returns>
        static GuideLine FindLineAtPosition(Vector2 mouseWorldPos)
        {
            float threshold = HandleUtility.GetHandleSize(mouseWorldPos) * 0.1f;

            foreach (var line in guideLines)
            {
                if (line.isHorizontal)
                {
                    if (Mathf.Abs(mouseWorldPos.y - line.positionInWorld) < threshold)
                    {
                        return line;
                    }
                }
                else
                {
                    if (Mathf.Abs(mouseWorldPos.x - line.positionInWorld) < threshold)
                    {
                        return line;
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 绘制标尺
        /// </summary>
        /// <param name="sceneView">当前的 SceneView</param>
        static void DrawRulers(SceneView sceneView)
        {
            // 获取 SceneView 的尺寸
            Rect sceneViewRect = sceneView.position;
            float width = sceneViewRect.width;
            float height = sceneViewRect.height;

            // 标尺参数
            float rulerThickness = 20f; // 标尺厚度
            Color rulerBackgroundColor = new Color(0.18f, 0.18f, 0.18f, 1f); // 标尺背景颜色
            Color rulerTickColor = Color.white; // 刻度颜色

            // 绘制水平和垂直标尺背景
            GUI.color = rulerBackgroundColor;
            GUI.DrawTexture(new Rect(0, 0, width, rulerThickness), EditorGUIUtility.whiteTexture); // 顶部标尺
            GUI.DrawTexture(new Rect(0, rulerThickness, rulerThickness, height - rulerThickness), EditorGUIUtility.whiteTexture); // 左侧标尺
            GUI.color = Color.white;

            // 刻度参数
            float smallTickInterval = 10f;  // 小刻度间隔
            float largeTickInterval = 50f;  // 大刻度间隔
            float smallTickLength = 5f;     // 小刻度长度
            float largeTickLength = 10f;    // 大刻度长度

            // 绘制水平标尺刻度
            for (float x = rulerThickness; x < width; x += smallTickInterval)
            {
                bool isLargeTick = Mathf.Abs((x - rulerThickness) % largeTickInterval) < 0.1f;
                float tickLength = isLargeTick ? largeTickLength : smallTickLength;

                Vector2 start = new Vector2(x, rulerThickness);
                Vector2 end = new Vector2(x, rulerThickness - tickLength);

                Handles.color = rulerTickColor;
                Handles.DrawLine(start, end);
            }

            // 绘制垂直标尺刻度
            for (float y = rulerThickness; y < height; y += smallTickInterval)
            {
                bool isLargeTick = Mathf.Abs((y - rulerThickness) % largeTickInterval) < 0.1f;
                float tickLength = isLargeTick ? largeTickLength : smallTickLength;

                Vector2 start = new Vector2(rulerThickness, y);
                Vector2 end = new Vector2(rulerThickness - tickLength, y);

                Handles.color = rulerTickColor;
                Handles.DrawLine(start, end);
            }
        }

        /// <summary>
        /// 绘制参考线
        /// </summary>
        /// <param name="sceneView">当前的 SceneView</param>
        static void DrawLines(SceneView sceneView)
        {
            if (sceneView.camera == null)
                return;

            Handles.BeginGUI();

            foreach (var line in guideLines)
            {
                // 将参考线的世界坐标转换为屏幕坐标
                Vector3 worldPos = line.isHorizontal
                    ? new Vector3(0, line.positionInWorld, 0)
                    : new Vector3(line.positionInWorld, 0, 0);

                Vector2 screenPosStart = HandleUtility.WorldToGUIPoint(worldPos);
                Vector2 screenPosEnd;

                if (line.isHorizontal)
                {
                    screenPosStart.x = 0;
                    screenPosEnd = new Vector2(sceneView.position.width, screenPosStart.y);
                }
                else
                {
                    screenPosStart.y = 0;
                    screenPosEnd = new Vector2(screenPosStart.x, sceneView.position.height);
                }

                // 设置参考线颜色
                Handles.color = line == curSelectedLine ? Color.blue : Color.cyan;

                // 绘制参考线
                Handles.DrawLine(screenPosStart, screenPosEnd);
            }

            Handles.EndGUI();
        }
    }
}